package com.gzc.just.play.client;

import com.beust.jcommander.JCommander;
import com.gzc.just.play.client.war3server.War3Server;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;

import java.net.*;
import java.util.concurrent.*;

/**
 * Client类是这个Warcraft III网络连接项目中的客户端实现，
 * 主要负责与服务器通信、管理与其他客户端的连接以及处理游戏数据转发。
 * <p>
 * 主要功能:
 * 1. 核心组件:
 * ConnectionServer: 负责与主服务器建立连接，接收服务器指令和IP列表
 * Broadcast: 监听本地6112端口的UDP广播数据包
 * War3Server: 为每个远程客户端创建的代理，处理TCP和UDP连接
 * <p>
 * 2. 工作流程:
 * 2.1 连接服务器
 * 首先，客户端连接到指定的服务器IP和端口(8888)，并发送标识码100表示这是客户端连接。服务器会返回一个IP列表，包括分配给该客户端的IP和其他已连接客户端的IP。
 * 2.2 处理服务器消息
 * 客户端接收三种类型的服务器消息：
 * 代码66: 通知客户端有新的Warcraft III连接请求
 * 代码88: 转发来自其他客户端的游戏数据
 * 代码99: 更新客户端列表
 * 2.3 管理War3Server连接
 * 对于每个远程客户端IP，Client会创建一个War3Server实例，包含：
 * TcpListener: 监听TCP连接，用于游戏数据传输
 * UdpListener: 监听UDP连接，用于游戏广播消息
 * 2.4 数据转发
 * 当本地Warcraft III客户端与其他远程客户端通信时，Client负责在各种连接之间转发数据：
 * 本地游戏客户端 ↔ 远程游戏客户端
 * UDP广播消息在客户端之间转发
 * <p>
 * 3. 特殊处理
 * 3.1 UDP广播处理
 * Client能够接收本地UDP广播包，并将其转发给所有连接的远程客户端：
 * Client.java - Broadcast类
 * if (packet.getAddress().getHostAddress().equals(inetAddress.getHostAddress())) {
 * // 只处理本地广播
 * for (War3Server value : connectionMap.values()) {
 * connectionServer.writeServer(value.ip, packet.getData(), packet.getLength());
 * }
 * }
 * 3.2 房间名称修改
 * 在转发UDP数据时，如果数据包长度超过100字节（通常是房间广播），Client会修改房间名称为"点击我加入房间"：
 * // Client.java - UdpListener.send方法
 * if(data.length>100){
 * //修改房间名
 * String text = "点击我加入房间";
 * byte[] customText = text.getBytes(StandardCharsets.UTF_8);
 * <p>
 * // 创建30字节数组，默认填充0x20（空格）
 * byte[] paddedBytes = new byte[31];
 * Arrays.fill(paddedBytes, (byte) 0x20);
 * <p>
 * // 拷贝原数据（确保不越界）
 * int copyLength = Math.min(customText.length, 31);
 * System.arraycopy(customText, 0, paddedBytes, 0, copyLength);
 * <p>
 * // 将修改后的名称写入数据包指定位置
 * for (int i = 20; i < paddedBytes.length +20; i++) {
 * data[i] = paddedBytes[i-20];
 * }
 * }
 * <p>
 * 总体架构
 * Client类实现了P2P网络代理功能，允许本地Warcraft III客户端通过服务器与其他远程客户端通信。
 * 它在本地创建虚拟网络接口，每个接口对应一个远程客户端，并在这些接口之间转发所有TCP和UDP数据，使得Warcraft III游戏认为所有玩家都在本地网络中。
 */
public class Client {
    private static final Logger logger = LogManager.getLogger(Client.class);
    
    //虚拟线程池
    public static final ExecutorService executor =
            Executors.newThreadPerTaskExecutor(Thread.ofVirtual().name("vThread-", 1).factory());
    /**
     * key:otherWar3Ip,value:远程war3 game客户端
     */
    public static final ConcurrentHashMap<String, War3Server> connectionMap = new ConcurrentHashMap<>();

    public static volatile String SERVER_IP = "192.168.56.1";
    public static volatile int SERVER_PORT;

    public static volatile InetAddress WAR3_LOCAL_IP;

    public static void main(String[] args) throws Exception {
        if (!initArgs(args)) {
            return;
        }

        var connectionServer = new ConnectionWar3ProxyServer();
        var broadcast = new LocalWar3GameBroadcast(connectionServer);

        executor.submit(connectionServer);
        Future<?> submit = executor.submit(broadcast);
        logger.info("client start ok,before submit Future get()");
        submit.get();
        logger.info("client main run over,after submit Future get()");
    }

    private static boolean initArgs(String[] args) throws UnknownHostException {
        var commandOptions = new ClientCommandOptions();
        JCommander commander = JCommander.newBuilder()
                .programName("war3proxy-client")
                .addObject(commandOptions)
                .build();
        commander.parse(args);
        if (commandOptions.isHelp()) {
            // 打印帮助信息
            commander.usage();
            return false;
        }

        final String serverIp = commandOptions.getServerIp();
        if (null != serverIp && !serverIp.isEmpty()) {
            logger.info("commandOptions serverIp={}", serverIp);
            SERVER_IP = serverIp;
        }

        final int serverPort = commandOptions.getServerPort();
        if (serverPort > 0 && serverPort < 65535) {
            logger.info("commandOptions serverPort={}", serverPort);
            SERVER_PORT = serverPort;
        }

        final InetAddress localHost = InetAddress.getLocalHost();
        logger.info("InetAddress.getLocalHost={}", localHost);
        WAR3_LOCAL_IP = localHost;
        return true;
    }

}